package model;

import jdd.bdd.BDD;
import network.*;
import org.jgrapht.alg.util.Pair;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class Model {
    public BDDEngine bddEngine;

    public HashMap<Ports, Integer> portsToPred;

    /**
     * Initialize the Network Model with topology and default rules.
     * A network owns a model.
     */
    public Model() {
        this.bddEngine = new BDDEngine();
        this.portsToPred = new HashMap<>();
    }


    public void update(Changes changes) {
        BDD bdd = this.bddEngine.bdd;
        changes.aggrBDDs();

        int cnt = 0;
        for (Map.Entry<Integer, ArrayList<Pair<Port, Port>>> entryI : changes.getAll().entrySet()) {
            // System.out.println("Round " + cnt++ + " with #EC " + portsToPred.size());

            HashSet<Ports> deletion = new HashSet<>();
            Port filterPort = entryI.getValue().get(0).getFirst();

            HashMap<Ports, Integer> oldPreds = (HashMap<Ports, Integer>) portsToPred.clone();
            for (Map.Entry<Ports, Integer> entry : oldPreds.entrySet()) {
                Ports from = entry.getKey();

                if (from.get(filterPort.getDevice().uid) != filterPort) continue;

                int oldBdd = portsToPred.get(from);
                int intersection = bdd.ref(bdd.and(oldBdd, entryI.getKey()));
                if (intersection == BDDEngine.BDDFalse) continue;

                int newBdd = bdd.ref(bdd.xor(oldBdd, intersection));
                bdd.ref(newBdd);
                portsToPred.replace(from, newBdd);
                if (newBdd == BDDEngine.BDDFalse) deletion.add(from);

                Ports to = from.replaceArray(entryI.getValue());
                if (portsToPred.containsKey(to)) {
                    oldBdd = portsToPred.get(to);
                    newBdd = bdd.ref(bdd.xor(oldBdd, intersection));
                    bdd.deref(oldBdd);
                    portsToPred.replace(to, newBdd);
                } else {
                    bdd.ref(intersection);
                    portsToPred.put(new Ports(to.ports), intersection);
                }

                from.reverseArray(entryI.getValue());
                bdd.deref(intersection);
            }

            for (Ports t : deletion) {
                if (portsToPred.get(t) == BDDEngine.BDDFalse) portsToPred.remove(t);
            }
        }
        changes.releaseBdd();
    }
}